﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.ComponentModel;
using DistributedGame.Service;

namespace DistributedGame.Server
{
    //[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
	//public class GameHost : IGameHost
	//{
	//    public static IGameClient CurrentClient
	//    {
	//        get { return OperationContext.Current.GetCallbackChannel<IGameClient>(); }
	//    }

	//    public ClientInfo Connect(ClientConnectRequest request)
	//    {
	//        System.Diagnostics.Debug.WriteLine("IGameHost.Connect from '" + request.Name + "'");
			
	//        return new ClientInfo() { Id = Guid.NewGuid().ToString(), Name=request.Name };
	//    }

	//    public void Say(string msg)
	//    {
	//        System.Diagnostics.Debug.WriteLine("IGameHost.Say '" + msg + "'");
			
	//        var prop = OperationContext.Current.IncomingMessageProperties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;
			
	//        var client = CurrentClient as IClientChannel;
	//        var id = OperationContext.Current.SessionId;
	//        var local = client.LocalAddress;
	//        var remote = client.RemoteAddress;
	//        var via = client.Via;
	//    }

	//    public void Whisper(string msg, ClientInfo receiver)
	//    {
	//        throw new NotImplementedException();
	//    }

	//    public void IsWriting(ClientInfo client)
	//    {
	//        throw new NotImplementedException();
	//    }

	//    public void Disconnect(ClientInfo client)
	//    {
	//        throw new NotImplementedException();
	//    }
	//}

	//public interface IGameClientChannel : IGameClientCallback, IClientChannel
	//{}

	internal static class CallbackExt
	{
		public static IClientChannel AsClientChannel(this IGameClientCallback callback)
		{
			return callback as IClientChannel;
		}
	}

	public class GameServerErrorHandler : IErrorHandler
	{
		public GameServer Server { get; private set; }

		public GameServerErrorHandler(GameServer server)
		{
			this.Server = server;
		}

		public bool HandleError(Exception error)
		{
			System.Diagnostics.Debug.WriteLine("Error caught: " + error.ToString());

			//TODO: how the hell can I know which client faulted?! I can't!

			//var client = this.Server.CurrentClient;
			//System.Diagnostics.Debug.WriteLine("This client faulted: " + client.Info.Nick);

			return true;//false;
		}

		public void ProvideFault(Exception error, MessageVersion version, ref System.ServiceModel.Channels.Message fault)
		{
			FaultException faultException = new FaultException(error.Message);
			MessageFault messageFault = faultException.CreateMessageFault();
			fault = System.ServiceModel.Channels.Message.CreateMessage(version, messageFault, faultException.Action);
		}
	}


	public class ErrorHandlerBahavior : IServiceBehavior
	{
		public IErrorHandler ErrorHandler { get; private set; }

		public ErrorHandlerBahavior(IErrorHandler errorHandler)
		{
			this.ErrorHandler = errorHandler;
		}

		void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
		{
		}

		void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
		{
			foreach (var dispatcher in serviceHostBase.ChannelDispatchers.OfType<ChannelDispatcher>()) {
				dispatcher.ErrorHandlers.Add(this.ErrorHandler);
			}
		}

		void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
		{
		}
	}


    public class GameServer : IDisposable
    {
		public ServiceHost ServiceHost { get; private set; }
		public GameHost GameHost { get; private set; }

		public CommunicationState State
		{
			get {
				var commObj = ServiceHost;
				if (commObj == null) return CommunicationState.Created;
				return commObj.State;
			}
		}

        public static IGameClientCallback GetCurrentClientCallback()
        {
            return OperationContext.Current.GetCallbackChannel<IGameClientCallback>();
        }

		public void StartServer(Uri baseAddress)//IGameHost gameHost, Uri baseAddress)
		{
			if (this.State != CommunicationState.Created)
			if (this.State != CommunicationState.Closed) throw new InvalidOperationException("Server can't be started at this state");
			
			//baseAddress.Scheme = "net.tcp";
			//baseAddress.Host = "localhost";
			
            //Uri tcpAdrs = new Uri("net.tcp://wn7-dptppd1:2718/game/");
            //Uri httpAdrs = new Uri("http://wn7-dptppd1:8080/game/");
            //Uri[] baseAdresses = { tcpAdrs };//, httpAdrs };
            
			var gameHost = new GameHost();
			//var gameHostAsSupportInitialize = gameHost as ISupportInitialize;
			//if (gameHostAsSupportInitialize != null) gameHostAsSupportInitialize.BeginInit();
			gameHost.SetOpening();

			this.ServiceHost = new ServiceHost(gameHost, baseAddress);
			//serviceHost.ser

            NetTcpBinding tcpBinding = new NetTcpBinding(SecurityMode.None, true);
            tcpBinding.ReliableSession.Enabled = true;
            
			tcpBinding.OpenTimeout = TimeSpan.FromSeconds(4);
			tcpBinding.CloseTimeout = TimeSpan.FromSeconds(2);
			tcpBinding.SendTimeout = TimeSpan.FromSeconds(4);
			tcpBinding.ReceiveTimeout = TimeSpan.MaxValue;
			tcpBinding.ReliableSession.InactivityTimeout = TimeSpan.FromSeconds(8);
			tcpBinding.ReliableSession.Ordered = true;

			ServiceHost.Description.Behaviors.Add(new ErrorHandlerBahavior(new GameServerErrorHandler(this)));
			
			ServiceHost.AddServiceEndpoint(typeof(IGameHost), tcpBinding, string.Empty);
			
			ServiceHost.Open();

			//if (gameHostAsSupportInitialize != null) gameHostAsSupportInitialize.EndInit();
			gameHost.SetOpen();

			this.GameHost = gameHost;
		}

		public void Dispose()
		{
			//TODO: change state to Closing while executing this function

			var gameHostAsDisposable = this.GameHost as IDisposable;
			var serviceHost = this.ServiceHost;

			if (serviceHost != null) {
			    if (serviceHost.State == CommunicationState.Opened) {
			        try
			        {
						if (gameHostAsDisposable != null) gameHostAsDisposable.Dispose();

			            serviceHost.Close();
			        } catch (CommunicationException) {
			            //Dangerously, ignore exceptions here
			            if (System.Diagnostics.Debugger.IsAttached) throw;
			        }
			    }

			    if (serviceHost.State != CommunicationState.Faulted) ((IDisposable)serviceHost).Dispose();
			}

			this.GameHost = null;
			this.ServiceHost = null;
		}
	}
}
